

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;

public class DynamicSql {

	private StringBuilder sql;
	private List<Object> parameters;

	public DynamicSql() {
		this.sql = new StringBuilder();
		this.parameters = new ArrayList<Object>();
	}

	public DynamicSql(String sql, Object... objs) {
		this.sql = new StringBuilder(sql);
		this.parameters = new ArrayList<Object>();
		this.parameters.addAll(Arrays.asList(objs));
	}

	public <E> DynamicSql(String sql, Map<String, E> params) {
		NameParameterSqlParser nameParameterSqlParser = new NameParameterSqlParser(sql);
		String useSql = nameParameterSqlParser.getUseSql();
		this.sql = new StringBuilder(useSql);
		this.parameters = new ArrayList<Object>();
		List<String> parameterNames = nameParameterSqlParser.getParameterNames();
		for (String parameterName : parameterNames) {
			this.parameters.add(readVal(params, parameterName));
		}
	}

	public DynamicSql appendIf(boolean flag, String sql) {
		if (flag) {
			this.sql.append(sql);
		}
		return this;
	}

	public <E> DynamicSql appendEach(Collection<E> objs, String sql, String split) {
		return appendEach(objs, sql, "", split, "");
	}

	public <E> DynamicSql appendEach(Collection<E> objs, String sql, String startStr, String split, String endStr) {
		if (startStr == null) {
			startStr = "";
		}
		if (split == null) {
			split = "";
		}
		if (endStr == null) {
			endStr = "";
		}
		this.sql.append(startStr);
		if (objs == null || objs.isEmpty()) {
			this.sql.append(endStr);
			return this;
		}
		if (sql == null || sql.length() == 0) {
			this.sql.append(endStr);
			return this;
		}
		NameParameterSqlParser nameParameterSqlParser = new NameParameterSqlParser(sql);
		String useSql = nameParameterSqlParser.getUseSql();
		List<String> parameterNames = nameParameterSqlParser.getParameterNames();
		boolean isFirst = true;
		for (E obj : objs) {
			if (isFirst) {
				isFirst = false;
			} else {
				this.sql.append(split);
			}
			this.sql.append(useSql);
			for (String parameterName : parameterNames) {
				this.parameters.add(eval(parameterName, obj));
			}
		}
		this.sql.append(endStr);
		return this;
	}

	private <E> Object eval(String exp, E obj) {
		if (exp == null || exp.length() == 0)
			return null;
		String[] names = exp.split("\\.");
		Object val = obj;
		for (String name : names) {
			if (val == null)
				return null;
			val = readVal(val, name);
		}
		return val;
	}

	private <E> Object readVal(E obj, String name) {
		if (obj instanceof Map) {
			return ((Map) obj).get(name);
		}
		try {
			Field f = obj.getClass().getDeclaredField(name);
			f.setAccessible(true);
			return f.get(obj);
		} catch (Exception e) {
			throw new RuntimeException(e);
			// return null;
		}
	}

	public <E> DynamicSql appendEach(E[] objs, String sql, String split) {
		return appendEach(objs, sql, "", split, "");
	}

	public <E> DynamicSql appendEach(E[] objs, String sql, String startStr, String split, String endStr) {
		return appendEach(Arrays.asList(objs), sql, startStr, split, endStr);
	}

	public List<Object> getParameters() {
		return parameters;
	}

	public <E> DynamicSql appendIn(Collection<E> objs) {
		this.sql.append(" in (");
		if (objs == null || objs.isEmpty()) {
			this.sql.append(")");
			return this;
		}
		boolean isFirst = true;
		for (E obj : objs) {
			if (isFirst) {
				isFirst = false;
			} else {
				this.sql.append(",");
			}
			this.sql.append("?");
			this.parameters.add(obj);
		}
		this.sql.append(")");
		return this;
	}

	public <E> DynamicSql appendIn(E[] objs) {
		return appendIn(Arrays.asList(objs));
	}

	public <E> DynamicSql appendIn(E[] objs, String sql) {
		return appendEach(objs, sql, " (", ",", ") ");
	}

	public <E> DynamicSql appendIn(Collection<E> objs, String sql) {
		return appendEach(objs, sql, " (", ",", ") ");
	}

	public String getSql() {
		return this.sql.toString();
	}

	public String toString() {
		if (this.sql == null || this.sql.length() == 0)
			return "";
		if (this.parameters == null)
			return "";
		return String.format("SQL ---------> %s \r\n"//
				/*      */ + "PARAMETERS --> %s", this.sql, this.parameters);
	}

}
